package com.tsfyun.scm.service.impl.common;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import com.tsfyun.common.base.dto.CheckTaskDTO;
import com.tsfyun.common.base.dto.EnumInfo;
import com.tsfyun.common.base.dto.TaskDTO;
import com.tsfyun.common.base.enums.domain.DomainOprationEnum;
import com.tsfyun.common.base.enums.domain.DomainTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.extension.IService;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.EntityUtil;
import com.tsfyun.common.base.util.SpringBeanUtils;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Description:
 * @since Created in 2020/4/3 09:52
 */
@Slf4j
@Service
public class CommonServiceImpl implements ICommonService {

    @Autowired
    private IStatusHistoryService statusHistoryService;

    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;

    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public <T> T changeDocumentStatus(TaskDTO dto) {
        DomainTypeEnum domainTypeEnum = DomainTypeEnum.of(dto.getDocumentClass());
        Optional.ofNullable(domainTypeEnum).orElseThrow(()->new ServiceException("单据类型错误"));
        //获取单据状态
        Map<String, List<EnumInfo>> statusMap = EntityUtil.getInstance().getStatusData();
        List<EnumInfo> domainStatusList = statusMap.get(dto.getDocumentClass());
        TsfPreconditions.checkArgument(CollUtil.isNotEmpty(domainStatusList),new ServiceException("未找到单据状态定义"));
        Map<String,EnumInfo> documentStatusMap = domainStatusList.stream().collect(Collectors.toMap(EnumInfo::getCode, Function.identity()));
        TsfPreconditions.checkArgument(documentStatusMap.containsKey(dto.getNewStatusCode()),new ServiceException("单据状态错误"));
        //检查新状态值是否正确
        //根据单据类型定位实体类
        Map<String,Class> classMaps = EntityUtil.getInstance().getData();
        Class domainClazz = classMaps.get(dto.getDocumentClass());
        //判断此单据是否有此状态字段
        List<String> fieldNames = Arrays.asList(domainClazz.getDeclaredFields()).stream().map(Field::getName).collect(Collectors.toList());
        TsfPreconditions.checkArgument(fieldNames.contains("statusId"),new ServiceException("此单据不允许此操作"));
        DomainOprationEnum oprationEnum = DomainOprationEnum.of(dto.getOperation());
        TsfPreconditions.checkArgument(Objects.nonNull(oprationEnum),new ServiceException("操作码错误"));
        //获取单据服务实现类
        String domainService = StringUtils.toLowerCaseFirstOne(dto.getDocumentClass()).concat("ServiceImpl");
        IService<T> o = (IService<T>) SpringBeanUtils.getBean(domainService);
        T entity = o.getById(dto.getDocumentId());
        Optional.ofNullable(entity).orElseThrow(()->new ServiceException("单据不存在，请刷新页面"));
        //获取旧状态
        String oldDocumentStatus =  ReflectUtil.invoke(entity,"getStatusId");
        //验证状态是否可以执行操作
        DomainStatus.getInstance().check(oprationEnum, oldDocumentStatus);
        TsfPreconditions.checkArgument(!Objects.equals(oldDocumentStatus,dto.getNewStatusCode()),new ServiceException("此操作已被被处理,请刷新页面"));
        //传入旧状态和单据id更新状态，悲观锁防止并发
        int updateCnt = o.updateDocumentStatus(entity,dto.getDocumentId(),oldDocumentStatus,dto.getNewStatusCode());
        TsfPreconditions.checkArgument(updateCnt > 0,new ServiceException("此操作已被处理,请刷新页面"));
        //更新状态
        ReflectUtil.invoke(entity,"setStatusId",dto.getNewStatusCode());
        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                dto.getDocumentId().toString(),domainClazz.getName(),
                oldDocumentStatus,StringUtils.isNotEmpty(oldDocumentStatus) ? documentStatusMap.get(oldDocumentStatus).getName() : "",
                dto.getNewStatusCode(),documentStatusMap.get(dto.getNewStatusCode()).getName(),
                dto.getMemo(),dto.getOperator());

        //异步删除处理任务通知，如果存在的话，父子线程已经能绑定同一个数据源了
        threadPoolTaskExecutor.execute(()->{
            try {
                taskNoticeContentService.deleteTaskNotice(dto.getDocumentClass(), dto.getDocumentId(), dto.getOperation());
            } finally {
            }
        });
        return entity;
    }

    @Override
    public <T> void checkDocumentStatus(CheckTaskDTO dto) {
        if(!"-1".equals(dto.getDocumentId())){
            DomainTypeEnum domainTypeEnum = DomainTypeEnum.of(dto.getDocumentClass());
            Optional.ofNullable(domainTypeEnum).orElseThrow(()->new ServiceException("单据类型错误"));
            //根据单据类型定位实体类
            Map<String,Class> classMaps = EntityUtil.getInstance().getData();
            Class domainClazz = classMaps.get(dto.getDocumentClass());
            //判断此单据是否有此状态字段
            List<String> fieldNames = Arrays.asList(domainClazz.getDeclaredFields()).stream().map(Field::getName).collect(Collectors.toList());
            TsfPreconditions.checkArgument(fieldNames.contains("statusId"),new ServiceException("此单据状态不允许此操作"));
            //获取单据服务实现类
            String domainService = StringUtils.toLowerCaseFirstOne(dto.getDocumentClass()).concat("ServiceImpl");
            IService<T> o = (IService<T>) SpringBeanUtils.getBean(domainService);
            Object entity = o.getById(dto.getDocumentId());
            Optional.ofNullable(entity).orElseThrow(() -> new ServiceException("单据不存在"));
            //获取旧状态
            String oldDocumentStatus = ReflectUtil.invoke(entity, "getStatusId");
            //验证状态是否可以执行操作
            DomainOprationEnum oprationEnum = DomainOprationEnum.of(dto.getOperation());
            TsfPreconditions.checkArgument(Objects.nonNull(oprationEnum), new ServiceException("操作码错误"));
            DomainStatus.getInstance().check(oprationEnum, oldDocumentStatus);
        }
    }

    @Override
    public <T> void checkDocumentStatus(CheckTaskDTO dto, Boolean handleTaskNotice) {
        checkDocumentStatus(dto);
        if(Objects.equals(handleTaskNotice,Boolean.TRUE)) {
            //删除任务
            log.info(String.format("操作员【%s】,任务操作码【%s】,单据类型【%s】，单据id【%s】", SecurityUtil.getCurrentPersonIdAndName(),dto.getOperation(),dto.getDocumentClass(),dto.getDocumentId()));
            taskNoticeContentService.deleteTaskNotice(dto.getDocumentClass(),dto.getDocumentId(),dto.getOperation());
        }
    }


}
